home *** CD-ROM | disk | FTP | other *** search
/ Openstep 4.2 (Developer) / Openstep Developer 4.2.iso / NextDeveloper / Examples / DriverKit / AMDPCSCSIDriver / AMDPCSCSIDriver_reloc.tproj / AMD_SCSI.m < prev    next >
Encoding:
Text File  |  1996-04-03  |  29.3 KB  |  1,210 lines

  1. /*
  2.  * AMD_SCSI.m - top-level module for AMD 53C974/79C974 PCI SCSI driver. 
  3.  *
  4.  * HISTORY
  5.  * 21 Oct 94    Doug Mitchell at NeXT
  6.  *      Created. 
  7.  */
  8.  
  9. #import "AMD_SCSI.h"
  10. #import "AMD_Private.h"
  11. #import "AMD_x86.h"
  12. #import "AMD_Chip.h"
  13. #import "AMD_ddm.h"
  14. #import "AMD_Regs.h"
  15. #import <mach/message.h>
  16. #import <driverkit/generalFuncs.h>
  17. #import <driverkit/interruptMsg.h>
  18. #import <driverkit/align.h>
  19. #import <driverkit/kernelDriver.h>
  20. #import <kernserv/prototypes.h>
  21.  
  22. static void AMDTimeout(void *arg);
  23.  
  24. /* 
  25.  * Template for command message sent to the I/O thread.
  26.  */
  27. static msg_header_t cmdMessageTemplate = {
  28.     0,                    // msg_unused 
  29.     1,                    // msg_simple 
  30.     sizeof(msg_header_t),            // msg_size 
  31.     MSG_TYPE_NORMAL,            // msg_type 
  32.     PORT_NULL,                // msg_local_port 
  33.     PORT_NULL,                // msg_remote_port - TO
  34.                         // BE FILLED IN 
  35.     IO_COMMAND_MSG                // msg_id 
  36. };
  37.  
  38. /*
  39.  * Template for timeout message.
  40.  */
  41. static msg_header_t timeoutMsgTemplate = {
  42.     0,                    // msg_unused 
  43.     1,                    // msg_simple 
  44.     sizeof(msg_header_t),            // msg_size 
  45.     MSG_TYPE_NORMAL,            // msg_type 
  46.     PORT_NULL,                // msg_local_port 
  47.     PORT_NULL,                // msg_remote_port - TO
  48.                         // BE FILLED IN 
  49.     IO_TIMEOUT_MSG                // msg_id 
  50. };
  51.  
  52.  
  53. @implementation AMD_SCSI
  54.  
  55. /*
  56.  * Create and initialize one instance of AMD_SCSI. The work is done by
  57.  * architecture- and chip-specific modules. 
  58.  */
  59. + (BOOL)probe:deviceDescription
  60. {
  61.     AMD_SCSI *inst = [self alloc];
  62.  
  63.  
  64.     if([inst archInit:deviceDescription] == nil) {
  65.         return NO;
  66.     }
  67.     else {
  68.         return YES;
  69.     }
  70. }
  71.  
  72. - free
  73. {
  74.     commandBuf cmdBuf;
  75.     
  76.     /*
  77.      * First kill the I/O thread if running. 
  78.      */
  79.     if(ioThreadRunning) {
  80.         cmdBuf.op = CO_Abort;
  81.         cmdBuf.scsiReq = NULL;
  82.         [self executeCmdBuf:&cmdBuf];
  83.     }
  84.     
  85.     if(commandLock) {
  86.         [commandLock free];
  87.     }
  88.     if(mdlFree) {
  89.         IOFree(mdlFree, MDL_SIZE * 2 * sizeof(vm_address_t));
  90.     }
  91.     return [super free];
  92. }
  93.  
  94. /*
  95.  * Our max DMA size is 64 K, derived from using 18 MDL entries (note the 
  96.  * first and last entries can refer to chunks as small as 4 bytes). 
  97.  */
  98. - (unsigned)maxTransfer
  99. {
  100.     return AMD_DMA_PAGE_SIZE * (MDL_SIZE - 2);
  101. }
  102.  
  103. /*
  104.  * Return required DMA alignment for current architecture.
  105.  */
  106. - (void)getDMAAlignment : (IODMAAlignment *)alignment;
  107. {
  108.     alignment->readStart   = AMD_READ_START_ALIGN;
  109.     alignment->writeStart  = AMD_WRITE_START_ALIGN;
  110.     alignment->readLength  = AMD_READ_LENGTH_ALIGN;
  111.     alignment->writeLength = AMD_WRITE_LENGTH_ALIGN;
  112. }
  113.  
  114. /*
  115.  * Statistics support.
  116.  */
  117. - (unsigned int) numQueueSamples
  118. {
  119.     return totalCommands;
  120. }
  121.  
  122.  
  123. - (unsigned int) sumQueueLengths
  124. {
  125.     return queueLenTotal;
  126. }
  127.  
  128.  
  129. - (unsigned int) maxQueueLength
  130. {
  131.     return maxQueueLen;
  132. }
  133.  
  134.  
  135. - (void)resetStats
  136. {
  137.     totalCommands = 0;
  138.     queueLenTotal = 0;
  139.     maxQueueLen   = 0;
  140. }
  141.  
  142. /*
  143.  * Do a SCSI command, as specified by an IOSCSIRequest. All the 
  144.  * work is done by the I/O thread.
  145.  */
  146. - (sc_status_t) executeRequest : (IOSCSIRequest *)scsiReq 
  147.             buffer : (void *)buffer 
  148.             client : (vm_task_t)client
  149. {
  150.     commandBuf cmdBuf;
  151.     
  152.     ddm_exp("executeRequest: cmdBuf 0x%x maxTransfer 0x%x\n", 
  153.         &cmdBuf, scsiReq->maxTransfer,3,4,5);
  154.     
  155.     bzero(&cmdBuf, sizeof(commandBuf));
  156.     cmdBuf.op      = CO_Execute;
  157.     cmdBuf.scsiReq = scsiReq;
  158.     cmdBuf.buffer  = buffer;
  159.     cmdBuf.client  = client;
  160.     scsiReq->driverStatus = SR_IOST_INVALID;
  161.     
  162.     [self executeCmdBuf:&cmdBuf];
  163.     
  164.     ddm_exp("executeRequest: cmdBuf 0x%x complete; driverStatus %s\n", 
  165.         &cmdBuf, IOFindNameForValue(scsiReq->driverStatus, 
  166.                     IOScStatusStrings), 3,4,5);
  167.     return cmdBuf.scsiReq->driverStatus;
  168. }
  169.  
  170.  
  171. /*
  172.  *  Reset the SCSI bus. All the work is done by the I/O thread.
  173.  */
  174. - (sc_status_t)resetSCSIBus
  175. {
  176.     commandBuf cmdBuf;
  177.     
  178.     ddm_exp("resetSCSIBus: cmdBuf 0x%x\n", &cmdBuf, 2,3,4,5);
  179.  
  180.     cmdBuf.op = CO_Reset;
  181.     cmdBuf.scsiReq = NULL;
  182.     [self executeCmdBuf:&cmdBuf];
  183.     ddm_exp("resetSCSIBus: cmdBuf 0x%x DONE\n", &cmdBuf, 2,3,4,5);
  184.     return SR_IOST_GOOD;        // can not fail
  185. }
  186.  
  187. /*
  188.  * The following 6 methods are all called from the I/O thread in 
  189.  * IODirectDevice. 
  190.  */
  191.  
  192. /*
  193.  * Called from the I/O thread when it receives an interrupt message.
  194.  * Currently all work is done by chip-specific module; maybe we should 
  195.  * put this method there....
  196.  */
  197. - (void)interruptOccurred
  198. {
  199.     #if    DDM_DEBUG
  200.     /*
  201.      * calculate interrupt service time if enabled.
  202.      */
  203.     ns_time_t    startTime, endTime, elapsedNs;
  204.     unsigned    elapsedUs = 0;
  205.     
  206.     if(IODDMMasks[AMD_DDM_INDEX] & DDM_INTR) {
  207.         IOGetTimestamp(&startTime);
  208.     }
  209.     ddm_thr("interruptOccurred: TOP\n", 1,2,3,4,5);
  210.     #endif    DDM_DEBUG
  211.  
  212.     [self hwInterrupt];
  213.     
  214.     #if    DDM_DEBUG
  215.     if(IODDMMasks[AMD_DDM_INDEX] & DDM_INTR) {
  216.         IOGetTimestamp(&endTime);
  217.         elapsedNs = endTime - startTime;
  218.         elapsedUs = (unsigned)((elapsedNs + 999ULL) / 1000ULL);
  219.     }
  220.     ddm_intr("interruptOccurred: DONE; elapsed time %d us\n", 
  221.         elapsedUs, 2,3,4,5);
  222.     #endif    DDM_DEBUG
  223. }
  224.  
  225. /*
  226.  * These three should not occur; they are here as error traps. All three are 
  227.  * called out from the I/O thread upon receipt of messages which it should
  228.  * not be seeing.
  229.  */
  230. - (void)interruptOccurredAt:(int)localNum
  231. {
  232.     IOLog("%s: interruptOccurredAt:%d\n", [self name], localNum);
  233. }
  234.  
  235. - (void)otherOccurred:(int)id
  236. {
  237.     IOLog("%s: otherOccurred:%d\n", [self name], id);
  238. }
  239.  
  240. - (void)receiveMsg
  241. {
  242.     IOLog("%s: receiveMsg\n", [self name]);
  243.     
  244.     /*
  245.      * We have to let IODirectDevice take care of this (i.e., dequeue the
  246.      * bogus message).
  247.      */
  248.     [super receiveMsg];
  249. }
  250.  
  251. /*
  252.  * Used in -timeoutOccurred to determine if specified cmdBuf has timed out.
  253.  * Returns YES if timeout, else NO.
  254.  */
  255. static inline BOOL
  256. isCmdTimedOut(commandBuf *cmdBuf, ns_time_t now)
  257. {
  258.     IOSCSIRequest    *scsiReq;
  259.     ns_time_t    expire;
  260.     
  261.     scsiReq = cmdBuf->scsiReq;
  262.     expire  = cmdBuf->startTime + 
  263.         (1000000000ULL * (unsigned long long)scsiReq->timeoutLength);
  264.     return ((now > expire) ? YES : NO);
  265. }
  266.  
  267. /*
  268.  * Called from the I/O thread when it receives a timeout
  269.  * message. We send these messages ourself from AMDTimeout().
  270.  */
  271. - (void)timeoutOccurred
  272. {
  273.     ns_time_t    now;
  274.     BOOL        cmdTimedOut = NO;
  275.     commandBuf    *cmdBuf;
  276.     commandBuf    *nextCmdBuf;
  277.     
  278.     ddm_thr("timeoutOccurred: TOP\n", 1,2,3,4,5);
  279.     IOGetTimestamp(&now);
  280.  
  281.     /*
  282.      *  Scan activeCmd and disconnectQ looking for tardy I/Os.
  283.      */
  284.     if(activeCmd) {
  285.         cmdBuf = activeCmd;
  286.         if(isCmdTimedOut(cmdBuf, now)) {
  287.             ddm_thr("activeCmd TIMEOUT, cmd 0x%x\n", 
  288.                 cmdBuf, 2,3,4,5);
  289.             activeCmd = NULL;
  290.             ASSERT(cmdBuf->scsiReq != NULL);
  291.             cmdBuf->scsiReq->driverStatus = SR_IOST_IOTO;
  292.             [self ioComplete:cmdBuf];
  293.             cmdTimedOut = YES;
  294.         }
  295.     }
  296.  
  297.     cmdBuf = (commandBuf *)queue_first(&disconnectQ);
  298.     while(!queue_end(&disconnectQ, (queue_entry_t)cmdBuf)) {
  299.         if(isCmdTimedOut(cmdBuf, now)) {
  300.             ddm_thr("disconnected cmd TIMEOUT, cmd 0x%x\n", 
  301.                 cmdBuf, 2,3,4,5);
  302.         
  303.             /*
  304.              *  Remove cmdBuf from disconnectQ and
  305.              *  complete it.
  306.              */
  307.             nextCmdBuf = (commandBuf *)queue_next(&cmdBuf->link);
  308.             queue_remove(&disconnectQ, cmdBuf, commandBuf *, link);
  309.             ASSERT(cmdBuf->scsiReq != NULL);
  310.             cmdBuf->scsiReq->driverStatus = SR_IOST_IOTO;
  311.             [self ioComplete:cmdBuf];
  312.             cmdBuf = nextCmdBuf;
  313.             cmdTimedOut = YES;
  314.         }
  315.         else {
  316.             cmdBuf = (commandBuf *)queue_next(&cmdBuf->link);
  317.         }
  318.     }
  319.  
  320.     /*
  321.      * Reset bus. This also completes all I/Os in disconnectQ with
  322.      * status CS_Reset.
  323.      */
  324.     if(cmdTimedOut) {
  325.         [self logRegs];
  326.         [self threadResetBus:NULL];
  327.     }
  328.     ddm_thr("timeoutOccurred: DONE\n", 1,2,3,4,5);
  329. }
  330.  
  331. /*
  332.  * Process all commands in commandQ. At most one of these will become
  333.  * activeCmd. The remainder of CO_Execute commands go to pendingQ. Other
  334.  * types of commands are executed immediately.
  335.  */
  336. - (void)commandRequestOccurred
  337. {
  338.     commandBuf *cmdBuf;
  339.     commandBuf *pendCmd;
  340.     
  341.     ddm_thr("commandRequestOccurred: top\n", 1,2,3,4,5);
  342.     [commandLock lock];
  343.     while(!queue_empty(&commandQ)) {
  344.         cmdBuf = (commandBuf *) queue_first(&commandQ);
  345.         queue_remove(&commandQ, cmdBuf, commandBuf *, link);
  346.         [commandLock unlock];
  347.         
  348.         switch(cmdBuf->op) {
  349.             case CO_Reset:
  350.                 /* 
  351.              * Note all active and disconnected commands will
  352.              * be terminted.
  353.              */
  354.                 [self threadResetBus:"Reset Command Received"];
  355.             [self ioComplete:cmdBuf];
  356.             break;
  357.             
  358.             case CO_Abort:
  359.             /*
  360.              * 1. Abort all active, pending, and disconnected
  361.              *    commands.
  362.              * 2. Notify caller of completion.
  363.              * 3. Self-terminate.
  364.              */
  365.             [self swAbort:SR_IOST_INT];
  366.             pendCmd = (commandBuf *)queue_first(&pendingQ);
  367.             while(!queue_end(&pendingQ, 
  368.                     (queue_entry_t)pendCmd)) {
  369.                 pendCmd->scsiReq->driverStatus = SR_IOST_INT;
  370.                 [self ioComplete:pendCmd];
  371.                 pendCmd = (commandBuf *)
  372.                     queue_next(&pendCmd->link);
  373.             }
  374.             [cmdBuf->cmdLock lock];
  375.             [cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
  376.             IOExitThread();
  377.             /* not reached */
  378.             
  379.             case CO_Execute:
  380.             [self threadExecuteRequest:cmdBuf];
  381.             break;
  382.             
  383.         }
  384.         [commandLock lock];
  385.     }
  386.     [commandLock unlock];
  387.     ddm_thr("commandRequestOccurred: DONE\n", 1,2,3,4,5);
  388.     return;
  389. }
  390.  
  391. /*
  392.  * Power management methods. All we care about is power off, when we must 
  393.  * reset the SCSI bus due to the Compaq BIOS's lack of a SCSI reset, which
  394.  * causes a hang if we have set up targets for sync data transfer mode.
  395.  */
  396. - (IOReturn)getPowerState:(PMPowerState *)state_p
  397. {
  398.      ddm_exp("getPowerState called\n", 1,2,3,4,5);
  399.        return IO_R_UNSUPPORTED;
  400. }
  401.  
  402. - (IOReturn)setPowerState:(PMPowerState)state
  403. {
  404. #ifdef DEBUG
  405.     IOLog("%s: received setPowerState: with %x\n", [self name],
  406.         (unsigned)state);
  407. #endif DEBUG
  408.     if (state == PM_OFF) {
  409.         // [self scsiReset];
  410.         [self powerDown];
  411.         return IO_R_SUCCESS;
  412.     }
  413.     return IO_R_UNSUPPORTED;
  414. }
  415.  
  416. - (IOReturn)getPowerManagement:(PMPowerManagementState *)state_p
  417. {
  418.     ddm_exp("getPowerManagement called\n", 1,2,3,4,5);
  419.         return IO_R_UNSUPPORTED;
  420. }
  421.  
  422. - (IOReturn)setPowerManagement:(PMPowerManagementState)state
  423. {
  424.     ddm_exp("setPowerManagement called\n", 1,2,3,4,5);
  425.         return IO_R_UNSUPPORTED;
  426. }
  427.  
  428. #if    AMD_ENABLE_GET_SET
  429.  
  430. - (IOReturn)setIntValues:(unsigned *)parameterArray
  431.     forParameter:(IOParameterName)parameterName
  432.     count:(unsigned int)count
  433. {
  434.         if(strcmp(parameterName, AMD_AUTOSENSE) == 0) {
  435.         if (count != 1) {
  436.             return IO_R_INVALID_ARG;
  437.         }
  438.         autoSenseEnable = (parameterArray[0] ? 1 : 0);
  439.         IOLog("%s: autoSense %s\n", [self name], 
  440.             (autoSenseEnable ? "Enabled" : "Disabled"));
  441.         return IO_R_SUCCESS;
  442.     }
  443.     else if(strcmp(parameterName, AMD_CMD_QUEUE) == 0) {
  444.         if (count != 1) {
  445.             return IO_R_INVALID_ARG;
  446.         }
  447.         cmdQueueEnable = (parameterArray[0] ? 1 : 0);
  448.         IOLog("%s: cmdQueue %s\n", [self name], 
  449.             (cmdQueueEnable ? "Enabled" : "Disabled"));
  450.         return IO_R_SUCCESS;
  451.     }
  452.     else if(strcmp(parameterName, AMD_SYNC) == 0) {
  453.         if (count != 1) {
  454.             return IO_R_INVALID_ARG;
  455.         }
  456.         syncModeEnable = (parameterArray[0] ? 1 : 0);
  457.         IOLog("%s: syncMode %s\n", [self name], 
  458.             (syncModeEnable ? "Enabled" : "Disabled"));
  459.         return IO_R_SUCCESS;
  460.     }
  461.     else if(strcmp(parameterName, AMD_FAST_SCSI) == 0) {
  462.         if (count != 1) {
  463.             return IO_R_INVALID_ARG;
  464.         }
  465.         fastModeEnable = (parameterArray[0] ? 1 : 0);
  466.         IOLog("%s: fastMode %s\n", [self name], 
  467.             (fastModeEnable ? "Enabled" : "Disabled"));
  468.         return IO_R_SUCCESS;
  469.     }
  470.     else if(strcmp(parameterName, AMD_RESET_TARGETS) == 0) {
  471.         int target;
  472.         perTargetData *perTargetPtr;
  473.         
  474.         if (count != 0) {
  475.             return IO_R_INVALID_ARG;
  476.         }
  477.         
  478.         /*
  479.          * Re-enable sync and command queueing. The
  480.          * disable bits persist after a reset.
  481.          */
  482.         for(target=0; target<SCSI_NTARGETS; target++) {
  483.             perTargetPtr = &perTarget[target];
  484.             perTargetPtr->cmdQueueDisable = 0;
  485.             perTargetPtr->syncDisable = 0;
  486.             perTargetPtr->maxQueue = 0;
  487.         }
  488.         IOLog("%s: Per Target disable flags cleared\n", [self name]);
  489.         return IO_R_SUCCESS;
  490.     }
  491.     else {
  492.         return [super setIntValues:parameterArray
  493.                 forParameter:parameterName
  494.                 count:count];
  495.     }
  496. }
  497.  
  498. - (IOReturn)getIntValues        : (unsigned *)parameterArray
  499.                forParameter : (IOParameterName)parameterName
  500.                       count : (unsigned *)count;    // in/out
  501. {    
  502.     if(strcmp(parameterName, AMD_AUTOSENSE) == 0) {
  503.         if(*count != 1) {
  504.             return IO_R_INVALID_ARG;
  505.         }
  506.         parameterArray[0] = autoSenseEnable;
  507.         return IO_R_SUCCESS;
  508.     }
  509.     else if(strcmp(parameterName, AMD_CMD_QUEUE) == 0) {
  510.         if(*count != 1) {
  511.             return IO_R_INVALID_ARG;
  512.         }
  513.         parameterArray[0] = cmdQueueEnable;
  514.         return IO_R_SUCCESS;
  515.     }
  516.     else if(strcmp(parameterName, AMD_SYNC) == 0) {
  517.         if(*count != 1) {
  518.             return IO_R_INVALID_ARG;
  519.         }
  520.         parameterArray[0] = syncModeEnable;
  521.         return IO_R_SUCCESS;
  522.     }
  523.     else if(strcmp(parameterName, AMD_FAST_SCSI) == 0) {
  524.         if(*count != 1) {
  525.             return IO_R_INVALID_ARG;
  526.         }
  527.         parameterArray[0] = fastModeEnable;
  528.         return IO_R_SUCCESS;
  529.     }
  530.     else {
  531.         return [super getIntValues : parameterArray
  532.             forParameter : parameterName
  533.             count : count];
  534.  
  535.     }
  536. }                    
  537.  
  538.  
  539. #endif    AMD_ENABLE_GET_SET
  540.  
  541. @end    /* AMD_SCSI */
  542.  
  543. @implementation AMD_SCSI(Private)
  544.  
  545. /*
  546.  * Private chip- and architecture-independent methods.
  547.  */
  548.  
  549. /*
  550.  * Pass one commandBuf to the I/O thread; wait for completion. 
  551.  * Normal completion status is in cmdBuf->scsiReq->driverStatus; 
  552.  * a non-zero return from this function indicates a Mach IPC error.
  553.  *
  554.  * This method allocates and frees cmdBuf->cmdLock.
  555.  */
  556. - (IOReturn)executeCmdBuf : (commandBuf *)cmdBuf
  557. {
  558.     msg_header_t msg = cmdMessageTemplate;
  559.     kern_return_t krtn;
  560.     IOReturn rtn = IO_R_SUCCESS;
  561.     
  562.     cmdBuf->cmdPendingSense = NULL;
  563.     cmdBuf->active = 0;
  564.     cmdBuf->cmdLock = [[NXConditionLock alloc] initWith:CMD_PENDING];
  565.     [commandLock lock];
  566.     queue_enter(&commandQ, cmdBuf, commandBuf *, link);
  567.     [commandLock unlock];
  568.     
  569.     /*
  570.      * Create a Mach message and send it in order to wake up the 
  571.      * I/O thread.
  572.      */
  573.     msg.msg_remote_port = interruptPortKern;
  574.     krtn = msg_send_from_kernel(&msg, MSG_OPTION_NONE, 0);
  575.     if(krtn) {
  576.         IOLog("%s: msg_send_from_kernel() returned %d\n", 
  577.             [self name], krtn);
  578.         rtn = IO_R_IPC_FAILURE;
  579.         goto out;
  580.     }
  581.     
  582.     /*
  583.      * Wait for I/O complete.
  584.      */
  585.     ddm_exp("executeCmdBuf: waiting for completion on cmdBuf 0x%x\n",
  586.         cmdBuf, 2,3,4,5);
  587.     [cmdBuf->cmdLock lockWhen:CMD_COMPLETE];
  588. out:
  589.     [cmdBuf->cmdLock free];
  590.     return rtn;
  591. }
  592.  
  593. /*
  594.  * Abort all active and disconnected commands with specified status. No 
  595.  * hardware action. Currently used by threadResetBus and during processing
  596.  * of a CO_Abort command.
  597.  */
  598. - (void)swAbort : (sc_status_t)status
  599. {
  600.     commandBuf *cmdBuf;
  601.     commandBuf *nextCmdBuf;
  602.     
  603.     ddm_thr("swAbort\n", 1,2,3,4,5);
  604.     if(activeCmd) {
  605.         activeCmd->scsiReq->driverStatus = status;
  606.         [self ioComplete:activeCmd];
  607.         activeCmd = NULL;
  608.     }
  609.     cmdBuf = (commandBuf *)queue_first(&disconnectQ);
  610.     while(!queue_end(&disconnectQ, (queue_entry_t)cmdBuf)) {
  611.         queue_remove(&disconnectQ, cmdBuf, commandBuf *, link);
  612.         nextCmdBuf = (commandBuf *)
  613.             queue_next(&cmdBuf->link);
  614.         cmdBuf->scsiReq->driverStatus = status;
  615.         [self ioComplete:cmdBuf];
  616.         cmdBuf = nextCmdBuf;
  617.     }
  618. #ifdef    DEBUG
  619.     /*
  620.      * activeArray "should be" empty...if not, make sure it is for debug.
  621.      */
  622.     {
  623.         int target, lun;
  624.         int active;
  625.         
  626.         for(target=0; target<SCSI_NTARGETS; target++) {
  627.         for(lun=0; lun<SCSI_NLUNS; lun++) {
  628.             active = activeArray[target][lun];
  629.             if(active) {
  630.                 IOLog("swAbort: activeArray[%d][%d] = %d\n",
  631.                 target, lun, active);
  632.             activeCount -= active;
  633.             activeArray[target][lun] = 0;
  634.             }
  635.         }
  636.         }
  637.         if(activeCount != 0) {
  638.             IOLog("swAbort: activeCount = %d\n", activeCount);
  639.         activeCount = 0;
  640.         }
  641.     }
  642. #endif    DEBUG
  643. }
  644.  
  645. /*
  646.  * Abort all active and disconnected commands with status SR_IOST_RESET.
  647.  * Reset hardware and SCSI bus. If there is a command in pendingQ, start
  648.  * it up.
  649.  */
  650. - (void)threadResetBus : (const char *)reason
  651. {
  652.     [self swAbort:SR_IOST_RESET];
  653.     [self hwReset : reason];
  654.     [self busFree];
  655. }
  656.  
  657. /*
  658.  * Commence processing of the specified command. 
  659.  *
  660.  * If activeCmd is non-NULL or cmdBufOK says we can't process this command,
  661.  * we just enqueue the command on the end of pendingQ. 
  662.  */
  663. - (void)threadExecuteRequest : (commandBuf *)cmdBuf
  664. {
  665.     #if    DDM_DEBUG
  666.     unsigned char target = cmdBuf->scsiReq->target;
  667.     unsigned char lun = cmdBuf->scsiReq->lun;
  668.     #endif    DDM_DEBUG
  669.     
  670.     if(activeCmd != NULL) {
  671.         ddm_thr("threadExecuteRequest: ACTIVE; adding 0x%x to "
  672.             "pendingQ\n", cmdBuf, 2,3,4,5);
  673.         queue_enter(&pendingQ, cmdBuf, commandBuf *, link);
  674.         return;
  675.     }    
  676.     else if([self cmdBufOK:cmdBuf] == NO) {
  677.         ddm_thr("threadExecuteRequest: !cmdBufOK; adding 0x%x to "
  678.             "pendingQ\n", cmdBuf, 2,3,4,5);
  679.         queue_enter(&pendingQ, cmdBuf, commandBuf *, link);
  680.         return;
  681.     }
  682.  
  683.     ddm_thr("calling hwStart: cmdBuf 0x%x activeArray[%d][%d] = %d\n",
  684.         cmdBuf, target, lun, activeArray[target][lun], 5);
  685.         
  686.     switch([self hwStart:cmdBuf]) {
  687.         case HWS_OK:        // cool
  688.         case HWS_BUSY:        // h/w can't take cmd now
  689.             break;
  690.         case HWS_REJECT:        // hw ready for new cmd
  691.         ddm_thr("threadExecuteRequest: calling busFree\n", 1,2,3,4,5);
  692.         [self busFree];
  693.     }
  694.     
  695.     ddm_thr("threadExecuteRequest(0x%x): DONE\n", cmdBuf, 2,3,4,5);
  696. }
  697.  
  698. /*
  699.  * Methods called by hardware-dependent modules.
  700.  */
  701.  
  702. #if    TEST_QUEUE_FULL
  703. int     testQueueFull;
  704. #endif    TEST_QUEUE_FULL
  705.  
  706. /*
  707.  * Called when a transaction associated with cmdBuf is complete. Notify 
  708.  * waiting thread. If cmdBuf->scsiReq exists (i.e., this is not a reset
  709.  * or an abort), scsiReq->driverStatus must be valid. If cmdBuf is active,
  710.  * caller must remove from activeCmd. We decrement activeArray[][] counter
  711.  * if appropriate.
  712.  */
  713. - (void)ioComplete:(commandBuf *)cmdBuf
  714. {
  715.     ns_time_t     currentTime;
  716.     IOSCSIRequest     *scsiReq = cmdBuf->scsiReq;
  717.     int         target;
  718.     int         lun;
  719.     
  720.     if(cmdBuf->cmdPendingSense != NULL) {
  721.         /*
  722.          * This was an autosense request.
  723.          */
  724.         
  725.         commandBuf *origCmdBuf = cmdBuf->cmdPendingSense;
  726.         esense_reply_t *alignedSense = cmdBuf->buffer;
  727.         
  728.         ASSERT(cmdBuf->scsiReq != NULL);
  729.         ddm_thr("autosense buf 0x%x complete for cmd 0x%x sense "
  730.             "key 0x%x\n",
  731.             cmdBuf, origCmdBuf, alignedSense->er_sensekey, 4,5);
  732.         if(cmdBuf->scsiReq->driverStatus == SR_IOST_GOOD) {
  733.             /*
  734.              * Copy aligned sense data to caller's buffer.
  735.              */
  736.             alignedSense = cmdBuf->buffer;
  737.             origCmdBuf->scsiReq->senseData = *alignedSense;
  738.             origCmdBuf->scsiReq->driverStatus = SR_IOST_CHKSV;
  739.         }
  740.         else {
  741.             IOLog("AMD53C974: Autosense request for target %d"
  742.                 " FAILED (%s)\n",
  743.                 cmdBuf->scsiReq->target, 
  744.                 IOFindNameForValue(scsiReq->driverStatus, 
  745.                     IOScStatusStrings));
  746.             origCmdBuf->scsiReq->driverStatus = SR_IOST_CHKSNV;
  747.         }
  748.         
  749.         /*
  750.          * Free all of the allocated memory associated with 
  751.          * this autosense request. 
  752.          */
  753.         [self deactivateCmd:cmdBuf];
  754.         IOFree(cmdBuf->scsiReq, sizeof(*cmdBuf->scsiReq));
  755.         IOFree(cmdBuf->unalignedSense, 
  756.             sizeof(esense_reply_t) + (2 * AMD_READ_START_ALIGN));
  757.         IOFree(cmdBuf, sizeof(commandBuf));
  758.         
  759.         /*
  760.          * Now complete the I/O for the original commandBuf.
  761.          */
  762.         [origCmdBuf->cmdLock lock];
  763.         [origCmdBuf->cmdLock unlockWith:YES];
  764.         return;
  765.     }
  766.     if(scsiReq != NULL) {
  767.         IOGetTimestamp(¤tTime);
  768.         scsiReq->totalTime = currentTime - cmdBuf->startTime;
  769.         scsiReq->bytesTransferred = 
  770.             scsiReq->maxTransfer - cmdBuf->currentByteCount;
  771.             
  772.         /*
  773.          * Catch bad SCSI status now.
  774.          */
  775.         if(scsiReq->driverStatus == SR_IOST_GOOD) {
  776.             #if    TEST_QUEUE_FULL
  777.             if(testQueueFull && 
  778.                (activeArray[scsiReq->target][scsiReq->lun] > 1)) {
  779.                 scsiReq->scsiStatus = STAT_QUEUE_FULL;
  780.                 testQueueFull = 0;
  781.             }
  782.             #endif    TEST_QUEUE_FULL
  783.             switch(scsiReq->scsiStatus) {
  784.                 case STAT_GOOD:
  785.                 break;
  786.                 case STAT_CHECK:
  787.                 if(autoSenseEnable &&
  788.                    (scsiReq->cdb.cdb_opcode != C6OP_TESTRDY)) {
  789.                     /*
  790.                      * Generate an autosense request, enqueue
  791.                      * on pendingQ. We skip this for Test 
  792.                      * Unit Ready commands to avoid unnecessary
  793.                      * Req Sense ops while polling removable
  794.                      * media drives. 
  795.                      */
  796.                     [self generateAutoSense:cmdBuf];
  797.                     if(cmdBuf->active) {
  798.                     [self deactivateCmd:cmdBuf];
  799.                     }
  800.                     return;
  801.                 }
  802.                 else {
  803.                     scsiReq->driverStatus = SR_IOST_CHKSNV;
  804.                 }
  805.                 break;
  806.                 
  807.                 case STAT_QUEUE_FULL:
  808.                     /*
  809.                  * Avoid notifying client of this condition;
  810.                  * update perTarget.maxQueue and place this 
  811.                  * request on pendingQ. We'll try this 
  812.                  * again when we ioComplete at least one
  813.                  * command in this target's queue.
  814.                  */
  815.                 if(cmdBuf->queueTag == QUEUE_TAG_NONTAGGED) {
  816.                     /*
  817.                      * Huh? We're not doing command
  818.                      * queueing...
  819.                      */
  820.                     scsiReq->driverStatus = SR_IOST_BADST;
  821.                     break;
  822.                 }
  823.                 target = scsiReq->target;
  824.                 lun = scsiReq->lun;
  825.                 if(cmdBuf->active) {
  826.                     [self deactivateCmd:cmdBuf];
  827.                 }
  828.                 perTarget[target].maxQueue = 
  829.                     activeArray[target][lun];
  830.                 ddm_thr("Target %d QUEUE FULL, maxQueue %d\n",
  831.                     target, perTarget[target].maxQueue,
  832.                     3,4,5);
  833.                 queue_enter(&pendingQ, cmdBuf, commandBuf *,
  834.                     link);
  835.                 return;
  836.                 
  837.                 default:
  838.                 scsiReq->driverStatus = SR_IOST_BADST;
  839.                 break;
  840.             }
  841.         }
  842.     }
  843.     if(cmdBuf->active) {
  844.         /*
  845.          * Note that the active flag is false for non-CO_Execute
  846.          * commands and commands aborted from pendingQ.
  847.          */
  848.         [self deactivateCmd:cmdBuf];
  849.     }
  850.     
  851.     #if    DDM_DEBUG
  852.     {
  853.         const char *status;
  854.         unsigned moved;
  855.         
  856.         if(scsiReq != NULL) {
  857.             status = IOFindNameForValue(scsiReq->driverStatus, 
  858.                 IOScStatusStrings);
  859.             moved = scsiReq->bytesTransferred;
  860.         }
  861.         else {
  862.             status = "Complete";
  863.             moved = 0;
  864.         }
  865.         ddm_thr("ioComplete: cmdBuf 0x%x status %s bytesXfr 0x%x\n", 
  866.             cmdBuf, status, moved,4,5);
  867.     }
  868.     #endif    DDM_DEBUG
  869.     
  870.     [cmdBuf->cmdLock lock];
  871.     [cmdBuf->cmdLock unlockWith:YES];
  872. }
  873.  
  874. /*
  875.  * Generate autosense request for specified cmdBuf, place it 
  876.  * at head of pendingQ.
  877.  */
  878. - (void)generateAutoSense : (commandBuf *)cmdBuf
  879. {
  880.     IOSCSIRequest     *scsiReq = cmdBuf->scsiReq;
  881.     commandBuf     *senseCmdBuf;
  882.     IOSCSIRequest     *senseScsiReq;
  883.     cdb_6_t        *cdbp;
  884.     
  885.     senseCmdBuf  = IOMalloc(sizeof(commandBuf));
  886.     senseScsiReq = IOMalloc(sizeof(IOSCSIRequest));
  887.     bzero(senseCmdBuf,  sizeof(commandBuf));
  888.     bzero(senseScsiReq, sizeof(IOSCSIRequest));
  889.     
  890.     /*
  891.      * commandBuf fields....
  892.      */
  893.     senseCmdBuf->cmdPendingSense = cmdBuf;
  894.     senseCmdBuf->op              = CO_Execute;
  895.     senseCmdBuf->scsiReq         = senseScsiReq;
  896.     
  897.     /*
  898.      * Get aligned sense buffer.
  899.      */
  900.     senseCmdBuf->unalignedSense = IOMalloc(sizeof(esense_reply_t) + 
  901.                     (2 * AMD_READ_START_ALIGN));
  902.     senseCmdBuf->buffer         = IOAlign(void *, 
  903.                     senseCmdBuf->unalignedSense,
  904.                     AMD_READ_START_ALIGN);
  905.     senseCmdBuf->client         = IOVmTaskSelf();
  906.     
  907.     /* 
  908.      * Now IOSCSIRequest fields for request sense.
  909.      */
  910.     senseScsiReq->target        = scsiReq->target;
  911.     senseScsiReq->lun           = scsiReq->lun;
  912.     senseScsiReq->read          = YES;
  913.     senseScsiReq->maxTransfer   = sizeof(esense_reply_t);
  914.     senseScsiReq->timeoutLength = 10;
  915.     senseScsiReq->disconnect    = 0;
  916.     
  917.     cdbp                 = &senseScsiReq->cdb.cdb_c6;
  918.     cdbp->c6_opcode         = C6OP_REQSENSE;
  919.     cdbp->c6_lun             = scsiReq->lun;
  920.     cdbp->c6_len             = sizeof(esense_reply_t);
  921.     senseScsiReq->driverStatus  = SR_IOST_INVALID;
  922.     
  923.     /*
  924.      * This goes at the head of pendingQ; hopefully it'll be the 
  925.      * next command out to the bus.
  926.      */
  927.     ddm_thr("generateAutoSense: autosense buf 0x%x enqueued for "
  928.         "cmdBuf 0x%x\n", senseCmdBuf, cmdBuf, 3,4,5);
  929.     queue_enter_first(&pendingQ, senseCmdBuf, commandBuf *, link);
  930.  
  931. }
  932.  
  933. /*
  934.  * I/O associated with activeCmd has disconnected. Place it on disconnectQ
  935.  * and enable another transaction.
  936.  */ 
  937. - (void)disconnect
  938. {
  939.     ddm_thr("DISCONNECT: cmdBuf 0x%x target %d lun %d tag %d\n",
  940.         activeCmd, activeCmd->scsiReq->target,
  941.         activeCmd->scsiReq->lun, activeCmd->queueTag, 5);
  942.     queue_enter(&disconnectQ,
  943.         activeCmd,
  944.         commandBuf *,
  945.         link);
  946.     #if    DDM_DEBUG
  947.     if((activeCmd->currentByteCount != activeCmd->scsiReq->maxTransfer) &&
  948.        (activeCmd->currentByteCount != 0)) {
  949.            ddm_thr("disconnect after partial DMA (max 0x%d curr 0x%x)\n",
  950.             activeCmd->scsiReq->maxTransfer, 
  951.             activeCmd->currentByteCount, 3,4,5);
  952.     }
  953.     #endif    DDM_DEBUG
  954.     /*
  955.      * Record this time so that activeCmd can be billed for
  956.      * disconnect latency at reselect time.
  957.      */
  958.     IOGetTimestamp(&activeCmd->disconnectTime);
  959.     activeCmd = NULL;
  960.     /* [self busFree]; NO! fsm does this at end of hwInterrupt! */
  961. }
  962.  
  963. /*
  964.  * Specified target, lun, and queueTag is trying to reselect. If we have 
  965.  * a commandBuf for this TLQ nexus on disconnectQ, remove it, make it the
  966.  * current activeCmd, and return YES. Else return NO.
  967.  * A value of zero for queueTag indicates a nontagged command (zero is never
  968.  * used as the queue tag value for a tagged command).
  969.  */
  970. - (BOOL)reselect : (unsigned char)target_id
  971.          lun : (unsigned char)lun
  972.         queueTag : (unsigned char)queueTag
  973. {
  974.     commandBuf *cmdBuf;
  975.     IOSCSIRequest *scsiReq;
  976.     ns_time_t currentTime;
  977.     
  978.     cmdBuf = (commandBuf *)queue_first(&disconnectQ);
  979.     while(!queue_end(&disconnectQ, (queue_t)cmdBuf)) {
  980.     
  981.         scsiReq = cmdBuf->scsiReq;
  982.         if((scsiReq->target == target_id) && 
  983.            (scsiReq->lun == lun) &&
  984.            (cmdBuf->queueTag == queueTag)) {
  985.             ddm_thr("RESELECT: target %d lun %d tag %d FOUND;"
  986.                 "cmdBuf 0x%x\n",
  987.                 target_id, lun, queueTag, cmdBuf, 5);
  988.             queue_remove(&disconnectQ,
  989.                 cmdBuf,
  990.                 commandBuf *,
  991.                 link);
  992.             activeCmd = cmdBuf;
  993.             
  994.             /*
  995.              * Bill this operation for latency time.
  996.              */
  997.             IOGetTimestamp(¤tTime);
  998.             scsiReq->latentTime += 
  999.                 (currentTime - activeCmd->disconnectTime);
  1000.             return(YES);
  1001.         }
  1002.         /*
  1003.          * Try next element in queue.
  1004.          */
  1005.         cmdBuf = (commandBuf *)cmdBuf->link.next;
  1006.     }
  1007.  
  1008.     /*
  1009.      * Hmm...this is not good! We don't want to talk to this target.
  1010.      */    
  1011.     IOLog("%s: ILLEGAL RESELECT target %d lun %d tag %d\n",
  1012.                 [self name], target_id, lun, queueTag);
  1013.     return(NO);
  1014. }
  1015.  
  1016. /*
  1017.  * Determine if activeArray[][], maxQueue, cmdQueueEnable, and a 
  1018.  * command's target and lun show that it's OK to start processing cmdBuf.
  1019.  * Returns YES if copacetic.
  1020.  */
  1021. - (BOOL)cmdBufOK : (commandBuf *)cmdBuf
  1022. {
  1023.     IOSCSIRequest     *scsiReq = cmdBuf->scsiReq;
  1024.     unsigned     target   = scsiReq->target;
  1025.     unsigned     lun      = scsiReq->lun;
  1026.     unsigned char    active;
  1027.     unsigned char    maxQ;
  1028.     
  1029.     active = activeArray[target][lun];
  1030.     if(active == 0) {
  1031.         /*
  1032.          * Trivial quiescent case, always OK.
  1033.          */
  1034.         return YES;
  1035.     }
  1036.     if((cmdQueueEnable == 0) ||
  1037.        (perTarget[target].cmdQueueDisable)) {
  1038.         /*
  1039.          * No command queueing (either globally or for this target),
  1040.          * only one at a time.
  1041.          */
  1042.         return NO;
  1043.     }
  1044.     maxQ = perTarget[target].maxQueue;
  1045.     if(maxQ == 0) {
  1046.         /*
  1047.          * We don't know what the target's limit is; go for it.
  1048.          */
  1049.         return YES;
  1050.     }
  1051.     if(active >= maxQ) {
  1052.         /*
  1053.          * T/L's queue full; hold off.
  1054.          */ 
  1055.         return NO;
  1056.     }    
  1057.     else {
  1058.         return YES;
  1059.     }
  1060. }
  1061.  
  1062. /*
  1063.  * The bus has gone free. Start up a command from pendingQ, if any, and
  1064.  * if allowed by cmdQueueEnable and activeArray[][].
  1065.  */
  1066. - (void)busFree
  1067. {
  1068.     commandBuf *cmdBuf;
  1069.     
  1070.     ASSERT(activeCmd == NULL);
  1071.     if(queue_empty(&pendingQ)) {
  1072.         ddm_thr("busFree: pendingQ empty\n", 1,2,3,4,5);
  1073.         return;
  1074.     }
  1075.     
  1076.     /*
  1077.      * Attempt to find a commandBuf in pendingQ which we are in a position
  1078.      * to process.
  1079.      */
  1080.     cmdBuf = (commandBuf *)queue_first(&pendingQ);
  1081.     while(!queue_end(&pendingQ, (queue_entry_t)cmdBuf)) {
  1082.         if([self cmdBufOK:cmdBuf]) {
  1083.             queue_remove(&pendingQ, cmdBuf, commandBuf *, link);
  1084.             ddm_thr("busFree: starting pending cmd 0x%x\n", cmdBuf,
  1085.                 2,3,4,5);
  1086.             [self threadExecuteRequest:cmdBuf];
  1087.             return;    
  1088.         }
  1089.         else {
  1090.             cmdBuf = (commandBuf *)queue_next(&cmdBuf->link);
  1091.         }
  1092.     }
  1093.     ddm_thr("busFree: pendingQ non-empty, no commands available\n", 
  1094.         1,2,3,4,5);
  1095. }
  1096.  
  1097. /*
  1098.  * Abort activeCmd (if any) and any disconnected I/Os (if any) and reset 
  1099.  * the bus due to gross hardware failure.
  1100.  * If activeCmd is valid, its scsiReq->driverStatus will be set to 'status'.
  1101.  */
  1102. - (void)hwAbort         : (sc_status_t)status
  1103.               reason : (const char *)reason
  1104. {
  1105.     if(activeCmd) {
  1106.         activeCmd->scsiReq->driverStatus = status;
  1107.         [self ioComplete:activeCmd];
  1108.         activeCmd = NULL;
  1109.     }
  1110.     [self logRegs];
  1111.     [self threadResetBus:reason];    
  1112. }
  1113.  
  1114. /*
  1115.  * Called by chip level to indicate that a command has gone out to the 
  1116.  * hardware.
  1117.  */
  1118. - (void)activateCommand : (commandBuf *)cmdBuf
  1119. {
  1120.     unsigned char target;
  1121.     unsigned char lun;
  1122.     
  1123.     /*
  1124.      * Start timeout timer for this I/O. The timer request is cancelled
  1125.      * in ioComplete.
  1126.      */
  1127.     cmdBuf->timeoutPort = interruptPortKern;
  1128.     #if    LONG_TIMEOUT
  1129.     cmdBuf->scsiReq->timeoutLength = OUR_TIMEOUT;
  1130.     #endif    LONG_TIMEOUT
  1131.     IOScheduleFunc(AMDTimeout, cmdBuf, cmdBuf->scsiReq->timeoutLength);
  1132.     
  1133.     /*
  1134.      * This is the only place where an activeArray[][] counter is 
  1135.      * incremented (and, hence, the only place where cmdBuf->active is 
  1136.      * set). The only other place activeCmd is set to non-NULL
  1137.      * is in reselect:lun:queueTag.
  1138.      */
  1139.     activeCmd = cmdBuf;
  1140.     target = cmdBuf->scsiReq->target;
  1141.     lun = cmdBuf->scsiReq->lun;
  1142.     activeArray[target][lun]++;
  1143.     activeCount++;
  1144.     cmdBuf->active = 1;
  1145.  
  1146.     /*
  1147.      * Accumulate statistics.
  1148.      */
  1149.     maxQueueLen = MAX(maxQueueLen, activeCount);
  1150.     queueLenTotal += activeCount;
  1151.     totalCommands++;
  1152.     ddm_thr("activateCommand: cmdBuf 0x%x target %d lun %d\n",
  1153.         cmdBuf, target, lun, 4,5);
  1154. }
  1155.  
  1156. /*
  1157.  * Remove specified cmdBuf from "active" status. Update activeArray,
  1158.  * activeCount, and unschedule pending timer.
  1159.  */
  1160. - (void)deactivateCmd : (commandBuf *)cmdBuf
  1161. {
  1162.     IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
  1163.     int target, lun;
  1164.     
  1165.     ASSERT(scsiReq != NULL);
  1166.     target = scsiReq->target;
  1167.     lun = scsiReq->lun;
  1168.     ddm_thr("deactivate cmdBuf 0x%x target %d lun %d activeArray %d\n",
  1169.         cmdBuf, target, lun, activeArray[target][lun], 5);
  1170.     ASSERT(activeArray[target][lun] != 0);
  1171.     activeArray[target][lun]--;
  1172.     ASSERT(activeCount != 0);
  1173.     activeCount--;
  1174.  
  1175.     /*
  1176.      * Cancel pending timeout request. Commands which timed out don't
  1177.      * have a timer request pending anymore.
  1178.      */
  1179.     if(scsiReq->driverStatus != SR_IOST_IOTO) {
  1180.         IOUnscheduleFunc(AMDTimeout, cmdBuf);
  1181.     }
  1182.     cmdBuf->active = 0;
  1183. }
  1184.  
  1185. @end    /* AMD_SCSI(Private) */
  1186.  
  1187. /*
  1188.  *  Handle timeouts.  We just send a timeout message to the I/O thread
  1189.  *  so it wakes up.
  1190.  */
  1191. static void AMDTimeout(void *arg)
  1192. {
  1193.     commandBuf     *cmdBuf = arg;
  1194.     msg_header_t    msg = timeoutMsgTemplate;
  1195.  
  1196.     ddm_err("AMDTimeout: cmdBuf 0x%x target %d\n", cmdBuf,
  1197.         cmdBuf->scsiReq->target, 3,4,5);
  1198.     if(!cmdBuf->active) {
  1199.         /*
  1200.          * Should never happen...
  1201.          */
  1202.         IOLog("AMD53C974: Timeout on non-active cmdBuf\n");
  1203.         return;
  1204.     }
  1205.     msg.msg_remote_port = cmdBuf->timeoutPort;
  1206.     IOLog("AMD53C974: SCSI Timeout\n");
  1207.     (void) msg_send_from_kernel(&msg, MSG_OPTION_NONE, 0);
  1208. }
  1209.  
  1210.